package in.srain.cube.concurrent;

import android.os.Handler;
import android.os.Looper;

import androidx.annotation.NonNull;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import in.srain.cube.util.Version;

public class AppThreads {

    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int DEFAULT_CORE_POOL_SIZE = CPU_COUNT + 1;
    private static final int DEFAULT_MAX_POOL_SIZE = CPU_COUNT * 2 + 1;

    private static final int KEEP_ALIVE_TIME = 1;
    private static final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;

    private static final Executor MAIN_EXECUTOR = new MainExecutor();
    private static final ThreadPoolExecutor DB_EXECUTOR = createThreadPool("cp-db-executor-", 1, 2);
    private static final ThreadPoolExecutor INIT_EXECUTOR = createThreadPool("cp-init-executor-", 0, 2);
    private static final ThreadPoolExecutor IO_EXECUTOR = createThreadPool("cp-io-executor-");

    public static ThreadPoolExecutor createThreadPool(String threadNamePrefix, int corePoolSize, int maxPoolSize) {
        final BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>();
        final ThreadFactory threadFactory = new DefaultThreadFactory(threadNamePrefix);
        final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT, workQueue, threadFactory);
        if (Version.hasGingerbread()) {
            threadPoolExecutor.allowCoreThreadTimeOut(true);
        }
        return threadPoolExecutor;
    }

    public static ThreadPoolExecutor createThreadPool(String threadNamePrefix) {
        return createThreadPool(threadNamePrefix, DEFAULT_CORE_POOL_SIZE, DEFAULT_MAX_POOL_SIZE);
    }

    public static void runOnMainThread(Runnable runnable) {
        MAIN_EXECUTOR.execute(ThreadPoolStatistics.createRunnableWrapper(runnable));
    }

    public static void runOnIOThread(Runnable runnable) {
        IO_EXECUTOR.execute(ThreadPoolStatistics.createRunnableWrapper(runnable));
        ThreadPoolStatistics.onTaskAdd(IO_EXECUTOR, "cp-io-executor-");
    }

    public static void runOnDBThread(Runnable runnable) {
        DB_EXECUTOR.execute(ThreadPoolStatistics.createRunnableWrapper(runnable));
        ThreadPoolStatistics.onTaskAdd(DB_EXECUTOR, "cp-db-executor-");
    }

    public static void runOnInitThread(Runnable runnable) {
        INIT_EXECUTOR.execute(ThreadPoolStatistics.createRunnableWrapper(runnable));
        ThreadPoolStatistics.onTaskAdd(INIT_EXECUTOR, "cp-init-executor-");
    }

    public static Executor getMainExecutor() {
        return MAIN_EXECUTOR;
    }

    private static class DefaultThreadFactory implements ThreadFactory {

        private static final AtomicInteger POOL_NUMBER = new AtomicInteger(1);
        private static final String POST = "-thread-";
        private final ThreadGroup mGroup;
        private final AtomicInteger mThreadNumber = new AtomicInteger(1);
        private final String mNamePrefix;

        private DefaultThreadFactory(String threadNamePrefix) {
            SecurityManager s = System.getSecurityManager();
            mGroup = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
            mNamePrefix = threadNamePrefix + POOL_NUMBER.getAndIncrement() + POST;
        }

        @Override
        public Thread newThread(@NonNull Runnable r) {
            Thread thread = new Thread(mGroup, r, mNamePrefix + mThreadNumber.getAndIncrement(), 0);
            if (thread.isDaemon()) {
                thread.setDaemon(false);
            }
            if (thread.getPriority() != Thread.NORM_PRIORITY) {
                thread.setPriority(Thread.NORM_PRIORITY);
            }
            return thread;
        }
    }

    private static class MainExecutor implements Executor {

        @Override
        public void execute(@NonNull Runnable command) {
            new Handler(Looper.getMainLooper()).post(command);
        }
    }
}
